Данные использовались в соревновании Ml Boot Camp 5.**

Вопросы в веб-форме.**

В соревновании предлагалось определить наличие/отсутствие сердечно-сосудистых заболеваний (ССЗ) по результатам осмотра пациента.

Описание данных.

Датасет сформирован из реальных клинических анализов, и в нём используются признаки, которые можно разбить на 3 группы:

Объективные признаки:

  • Возраст (age)
  • Рост (height)
  • Вес (weight)
  • Пол (gender)

Результаты измерения:

  • Артериальное давление верхнее и нижнее (ap_hi, ap_lo)
  • Холестерин (cholesterol)
  • Глюкоза (gluc)

Субъективные признаки (со слов пациентов):

  • Курение (smoke)
  • Употребление алкоголя (alco)
  • Физическая активность (active)

Целевой признак (который интересно будет прогнозировать):

  • Наличие сердечно-сосудистых заболеваний по результатам классического врачебного осмотра (cardio)

Значения показателей холестерина и глюкозы представлены одним из трех классов: норма, выше нормы, значительно выше нормы. Значения субъективных признаков — бинарны.

Все показатели даны на момент осмотра.

Мы будем работать только с обучающей выборкой и с помощью Pandas проведем первичный анализ данных.

Из библиотек нам понадобятся только NumPy и Pandas.


In [1]:
import numpy as np
import pandas as pd

Считываем данные из CSV-файла в объект pandas DataFrame.


In [68]:
df = pd.read_csv('mlbootcamp5_train.csv', sep=';', 
                 index_col='id')

Посмотрим на первые 5 записей.


In [4]:
df.head()


Out[4]:
age gender height weight ap_hi ap_lo cholesterol gluc smoke alco active cardio
id
0 18393 2 168 62.0 110 80 1 1 0 0 1 0
1 20228 1 156 85.0 140 90 3 1 0 0 1 1
2 18857 1 165 64.0 130 70 3 1 0 0 0 1
3 17623 2 169 82.0 150 100 1 1 0 0 1 1
4 17474 1 156 56.0 100 60 1 1 0 0 0 0

Вопрос 1 (1 балл). Сколько мужчин и женщин представлено в этом наборе данных? Не было дано расшифровки признака "пол" (какому полу соответствует 1, а какому – 2 в признаке gender) – это определите, посмотрев также на рост при разумном предположении, что в среднем мужчины выше (здесь и далее под средним понимается среднее арифметическое).

Варианты:

  • 45470 женщин и 24530 мужчин
  • 45470 мужчин и 24530 женщин
  • 45530 женщин и 24470 мужчин
  • 45530 мужчин и 24470 женщин

In [5]:
df[['height', 'gender']].groupby('gender').mean()


Out[5]:
height
gender
1 161.355612
2 169.947895

In [7]:
df.groupby('gender').count()


Out[7]:
age height weight ap_hi ap_lo cholesterol gluc smoke alco active cardio
gender
1 45530 45530 45530 45530 45530 45530 45530 45530 45530 45530 45530
2 24470 24470 24470 24470 24470 24470 24470 24470 24470 24470 24470

In [ ]:
24470 - M & 45530 Ж

Вопрос 2 (1 балл). Кто в среднем реже указывает, что употребляет алкоголь – мужчины или женщины?

Варианты:

  • мужчины
  • женщины

In [22]:
df.groupby('gender')['alco'].sum()/df.groupby('gender')['alco'].count()


Out[22]:
gender
1    0.025500
2    0.106375
Name: alco, dtype: float64

Вопрос 3 (1 балл). Во сколько раз (округленно, round) процент курящих среди мужчин больше, чем процент курящих среди женщин (по крайней мере, по этим анкетным данным)?

Варианты:

  • 4
  • 8
  • 12
  • 16

In [27]:
prop_smoke = df.groupby('gender')['smoke'].sum()/df.groupby('gender')['smoke'].count()
prop_smoke


Out[27]:
gender
1    0.017856
2    0.218880
Name: smoke, dtype: float64

In [32]:
round(prop_smoke[2]/prop_smoke[1])


Out[32]:
12.0

Вопрос 4 (1 балл). Вы наверняка заметили, что значения возраста какие-то странные. Догадайтесь, в чём здесь измеряется возраст, и ответьте, на сколько месяцев (примерно) отличаются медианные значения возраста курящих и некурящих.

Варианты:

  • 10
  • 15
  • 20
  • 25

In [39]:
round((df.groupby('smoke')['age'].median()[0]  - df.groupby('smoke')['age'].median()[1])/30)
#median


Out[39]:
20.0

Вопрос 5 (2 балла). В статье на Википедии про сердечно-сосудистый риск показана шкала SCORE для расчёта риска смерти от сердечно-сосудистого заболевания в ближайшие 10 лет. Вот она:

Давайте посмотрим на правый верхний прямоугольник, отображающий сегмент курящих мужчин в возрасте от 60 до 64 лет включительно. (Неочевидно, но тут для возраста и давления цифры означают верхнюю границу, и она не включается).

Видим 9-ку в левом нижнем углу этого прямоугольника и 47 – в правом верхнем. То есть если при этом систолическое (т.е. верхнее) артериальное давление – меньше 120 мм рт.ст., а уровень холестерина – 4 ммоль/л, то риск ССЗ оценивается примерно в 5 раз ниже, чем если бы давление лежало в интервале [160, 180), а холестерина было бы 8 ммоль/л.

Давайте посчитаем аналогичное значение, но на наших данных.

Уточнения:

  • Создайте новый признак age_years – возраст в годах, округлив до целых (round). Для данного примера отберите курящих мужчин от 60 до 64 лет включительно
  • Категории уровня холестрина на рисунке и в наших данных отличаются. Отображение значений на картинке в значения признака cholesterol следующее: 4 ммоль/л $\rightarrow$ 1, 5-7 ммоль/л $\rightarrow$ 2, 8 ммоль/л $\rightarrow$ 3.
  • Интересуют 2 подвыборки курящих мужчин возраста от 60 до 64 лет включительно: первая с верхним артериальным давлением строго меньше 120 мм рт.ст. и концентрацией холестерина – 4 ммоль/л, а вторая – с верхним артериальным давлением от 160 (включительно) до 180 мм рт.ст. (не включительно) и концентрацией холестерина – 8 ммоль/л.

Во сколько раз (округленно, round) отличаются доли больных людей (согласно целевому признаку, cardio) в этих двух подвыборках? Посчитайте на наших данных.

Варианты:

  • 2
  • 3
  • 4
  • 5

In [43]:
year_duration = 365.25
month_duration = 30.4167

In [66]:
df['age_years'] = round(df.age / year_duration).astype('int')
data = df[(df.smoke == 1) & (df.gender == 2) & (60 <= df.age_years) & (df.age_years < 65)]
part1 = data[(data.ap_hi < 120) & (data.cholesterol == 1)]
part2 = data[(160 <= data.ap_hi) & (data.ap_hi < 180) & (data.cholesterol == 3)]
part2.cardio.mean() / part1.cardio.mean()


Out[66]:
3.281818181818182

Вопрос 6 (2 балла). Постройте новый признак – BMI (Body Mass Index). Для этого надо вес в килограммах поделить на квадрат роста в метрах. Нормальными считаются значения BMI от 18.5 до 25. Выберите верные утверждения.

Утверждения:

  • Медианный BMI по выборке превышает норму
  • У женщин в среднем BMI ниже, чем у мужчин
  • У здоровых в среднем BMI выше, чем у больных
  • В сегменте здоровых и непьющих мужчин в среднем BMI ближе к норме, чем в сегменте здоровых и непьющих женщин

In [69]:
df['BMI'] = df.weight / ((df.height / 100) ** 2)
print("median: " + str(df.BMI.median()))
print("women mean: " + str(df[df.gender == 1].BMI.mean()))
print("men mean: " + str(df[df.gender == 2].BMI.mean()))
print("healty mean: " + str(df[df.cardio == 0].BMI.mean()))
print("sick mean: " + str(df[df.cardio == 1].BMI.mean()))
print("teetotal healthy men mean: " + str(df[(df.gender == 2) & (df.alco == 0) & (df.cardio == 0)].BMI.mean()))
print("teetotal healthy women mean: " + str(df[(df.gender == 1) & (df.alco == 0) & (df.cardio == 0)].BMI.mean()))


median: 26.374068120774975
women mean: 27.98758344183312
men mean: 26.754442357289474
healty mean: 26.54817520679475
sick mean: 28.566060627015435
teetotal healthy men mean: 25.872638075460173
teetotal healthy women mean: 26.845406594131518

Вопрос 7 (2 балла). Можно заметить, что данные не особо-то чистые, много в них всякой "грязи" и неточностей. Еще лучше мы это увидим, когда обсудим визуализацию данных.

Отфильтруйте следующие сегменты пациентов (считаем это ошибками в данных)

  • указанное нижнее значение артериального давления строго выше верхнего
  • рост строго меньше 2.5%-перцентили или строго больше 97.5%-перцентили (используйте pd.Series.quantile, если не знаете, что это такое – прочитайте)
  • вес строго меньше 2.5%-перцентили или строго больше 97.5%-перцентили

Это вовсе не вся чистка данных, которую можно было проделать, но пока остановимся на этом.

Сколько процентов данных (округленно, round) мы выбросили?

Варианты:

  • 8
  • 9
  • 10
  • 11

In [70]:
height_low, height_high = df.height.quantile([.025, .975])
weight_low, weight_high = df.weight.quantile([.025, .975])

cond1 = (df.ap_lo > df.ap_hi)
cond2 = ((df.height < height_low) | (height_high < df.height))
cond3 = ((df.weight < weight_low) | (weight_high < df.weight))
unclean = (cond1 | cond2 | cond3)

print(unclean.sum())
print(df.shape[0])
print(unclean.sum() / df.shape[0])
df = df[~unclean]
print(df.shape[0])


6741
70000
0.0963
63259

In [49]:
5033/63259


Out[49]:
0.07956180148279296

In [51]:
# print(df.count())
filter_pressure = df[df.ap_hi > df.ap_lo]
# print(filter_pressure.count())
filter_height = filter_pressure[(filter_pressure.height < filter_pressure.height.quantile(0.025)) | (filter_pressure.height > filter_pressure.height.quantile(0.975))]
 # print(filter_height.count())
filter_weight = filter_height[(filter_height.weight < filter_height.weight.quantile(0.025)) | (filter_height.weight > filter_height.weight.quantile(0.975))]
print((df.count() - filter_weight.count())/df.count())


age            0.998157
gender         0.998157
height         0.998157
weight         0.998157
ap_hi          0.998157
ap_lo          0.998157
cholesterol    0.998157
gluc           0.998157
smoke          0.998157
alco           0.998157
active         0.998157
cardio         0.998157
dtype: float64

In [59]:
dfClear = df[(df['ap_lo']<=df['ap_hi']) & (df['height'] >= df['height'].quantile(0.025)) 
             & (df['height'] <= df['height'].quantile(0.975)) 
             & (df['weight'] >= df['weight'].quantile(0.025)) 
             & (df['weight'] <= df['weight'].quantile(0.975))]

In [60]:
dfClear.shape


Out[60]:
(63259, 12)

In [61]:
len(dfClear)/len(df)


Out[61]:
0.9037